{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "963082cc",
   "metadata": {},
   "source": [
    "# RunnableLambda\n",
    "\n",
    "`RunnableLambda` 는 **사용자 정의 함수를 실행** 할 수 있는 기능을 제공합니다.\n",
    "\n",
    "이를 통해 개발자는 **자신만의 함수를 정의**하고, 해당 함수를 `RunnableLambda` 를 사용하여 실행할 수 있습니다.\n",
    "\n",
    "예를 들어, 데이터 전처리, 계산, 또는 외부 API와의 상호 작용과 같은 작업을 수행하는 함수를 정의하고 실행할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "25e55372",
   "metadata": {},
   "source": [
    "## 사용자 정의 함수를 실행하는 방법\n",
    "\n",
    "**주의사항**\n",
    "\n",
    "사용자 정의 함수를 `RunnableLambda`로 래핑하여 활용할 수 있는데, 여기서 주의할 점은 **사용자 정의 함수가 받을 수 있는 인자는 1개 뿐이라는 점** 입니다.\n",
    "\n",
    "만약, 여러 인수를 받는 함수로 구현하고 싶다면, 단일 입력을 받아들이고 이를 여러 인수로 풀어내는 래퍼를 작성해야 합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23ae717a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API 키를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API 키 정보 로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2019ad82",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH13-LCEL-Advanced\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "44c99a82",
   "metadata": {},
   "outputs": [],
   "source": [
    "from operator import itemgetter\n",
    "\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnableLambda\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "\n",
    "def length_function(text):  # 텍스트의 길이를 반환하는 함수\n",
    "    return len(text)\n",
    "\n",
    "\n",
    "def _multiple_length_function(text1, text2):  # 두 텍스트의 길이를 곱하는 함수\n",
    "    return len(text1) * len(text2)\n",
    "\n",
    "\n",
    "def multiple_length_function(  # 2개 인자를 받는 함수로 연결하는 wrapper 함수\n",
    "    _dict,\n",
    "):  # 딕셔너리에서 \"text1\"과 \"text2\"의 길이를 곱하는 함수\n",
    "    return _multiple_length_function(_dict[\"text1\"], _dict[\"text2\"])\n",
    "\n",
    "\n",
    "# 프롬프트 템플릿 생성\n",
    "prompt = ChatPromptTemplate.from_template(\"what is {a} + {b}?\")\n",
    "# ChatOpenAI 모델 초기화\n",
    "model = ChatOpenAI()\n",
    "\n",
    "# 프롬프트와 모델을 연결하여 체인 생성\n",
    "chain1 = prompt | model\n",
    "\n",
    "# 체인 구성\n",
    "chain = (\n",
    "    {\n",
    "        \"a\": itemgetter(\"input_1\") | RunnableLambda(length_function),\n",
    "        \"b\": {\"text1\": itemgetter(\"input_1\"), \"text2\": itemgetter(\"input_2\")}\n",
    "        | RunnableLambda(multiple_length_function),\n",
    "    }\n",
    "    | prompt\n",
    "    | model\n",
    "    | StrOutputParser()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "959d3031",
   "metadata": {},
   "source": [
    "chain 을 실행하여 결과를 확인합니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "60df37cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 주어진 인자들로 체인을 실행합니다.\n",
    "chain.invoke({\"input_1\": \"bar\", \"input_2\": \"gah\"})"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b5f079c",
   "metadata": {},
   "source": [
    "## RunnableConfig 인자로 활용\n",
    "\n",
    "`RunnableLambda` 는 선택적으로 [RunnableConfig](https://api.python.langchain.com/en/latest/runnables/langchain_core.runnables.config.RunnableConfig.html#langchain_core.runnables.config.RunnableConfig) 를 수용할 수 있습니다.\n",
    "\n",
    "이를 통해 콜백, 태그 및 기타 구성 정보를 중첩된 실행에 전달할 수 있습니다.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e319229",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.runnables import RunnableConfig\n",
    "import json\n",
    "\n",
    "\n",
    "def parse_or_fix(text: str, config: RunnableConfig):\n",
    "    # 다음 텍스트를 수정하는 프롬프트 템플릿을 생성합니다.\n",
    "    fixing_chain = (\n",
    "        ChatPromptTemplate.from_template(\n",
    "            \"Fix the following text:\\n\\ntext\\n{input}\\n\\nError: {error}\"\n",
    "            \" Don't narrate, just respond with the fixed data.\"\n",
    "        )\n",
    "        | ChatOpenAI()\n",
    "        | StrOutputParser()\n",
    "    )\n",
    "    # 최대 3번 시도합니다.\n",
    "    for _ in range(3):\n",
    "        try:\n",
    "            # JSON 형식으로 텍스트를 파싱합니다.\n",
    "            return json.loads(text)\n",
    "        except Exception as e:\n",
    "            # 파싱 중 오류가 발생하면 수정 체인을 호출하여 텍스트를 수정합니다.\n",
    "            text = fixing_chain.invoke({\"input\": text, \"error\": e}, config)\n",
    "            print(f\"config: {config}\")\n",
    "    # 파싱에 실패한 경우 \"Failed to parse\" 문자열을 반환합니다.\n",
    "    return \"Failed to parse\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c8f4269a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.callbacks import get_openai_callback\n",
    "\n",
    "with get_openai_callback() as cb:\n",
    "    # RunnableLambda를 사용하여 parse_or_fix 함수를 호출합니다.\n",
    "    output = RunnableLambda(parse_or_fix).invoke(\n",
    "        input=\"{foo:: bar}\",\n",
    "        config={\"tags\": [\"my-tag\"], \"callbacks\": [cb]},  # config 를 전달합니다.\n",
    "    )\n",
    "    # 수정한 결과를 출력합니다.\n",
    "    print(f\"\\n\\n수정한결과:\\n{output}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "225f9792",
   "metadata": {},
   "outputs": [],
   "source": [
    "output"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "py-test",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
